﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace uk.ac.soton.ses
{
    /// <summary>
    /// Provides a wrapper class for parsing a delimited file with a specifiable delimiter, header line and first line
    /// </summary>
    public class FileParser_DelimitedFile: IDisposable
    {
        private string _fileName;
        private char _delim;
        private int _headerLineNo;
        private int _firstDataRowLineNo;

        private StreamReader _streamReader = null;

        /// <summary>
        /// Field names, zero-based indexed by field/row number
        /// </summary>
        public Dictionary<int, string> FieldNames { get; set; }

        /// <summary>
        /// The number of lines found in the parsed file
        /// </summary>
        public int LineCount { get; set; }

        /// <summary>
        /// The number of fields found in the parsed file
        /// </summary>
        public int FieldCount { get; set; }

        /// <summary>
        /// When reading the file line by line, this returns the current line number relative to the beginning of the file
        /// </summary>
        public int CurrentLineNoAbs { get; set; }

        /// <summary>
        /// When reading the file line by line, this returns the current line number relative to the first requested line
        /// </summary>
        public int CurrentLineNoRel { get; set; }

        /// <summary>
        /// The value of the current line before it is split into fields by the delimiter
        /// </summary>
        public string CurrentLineContents { get; set; }

        /// <summary>
        /// A dictionary with a zero-based key (field number) with a value of the current field after the delimiter is applied
        /// </summary>
        public Dictionary<int, string> CurrentFieldValues { get; set; }

        /// <summary>
        /// Constructor with parameters relating to how to parse the file with no header row
        /// </summary>
        /// <param name="fileName">The path to the file to parse</param>
        /// <param name="delim">The delimiter to use</param>
        /// <param name="firstDataRowLineNo">The first line in the file that contains data</param>
        public FileParser_DelimitedFile(string fileName, char delim, int firstDataRowLineNo)
            : this(fileName, delim, firstDataRowLineNo, -1)
        {
        }

        /// <summary>
        /// Constructor with parameters relating to how to parse the file with a header row for field names
        /// </summary>
        /// <param name="fileName">The path to the file to parse</param>
        /// <param name="delim">The delimiter to use</param>
        /// <param name="firstDataRowLineNo">The first line in the file that contains data</param>
        /// <param name="headerLineNo">The line in the file that contains field names</param>
        public FileParser_DelimitedFile(string fileName, char delim, int firstDataRowLineNo, int headerLineNo)
        {
            this._fileName = fileName;
            this._delim = delim;
            this._headerLineNo = headerLineNo;
            this._firstDataRowLineNo = firstDataRowLineNo;
            this.ReadHeaderAndStats();
        }

        /// <summary>
        /// Initialise the class by reading the file using the settings provided.
        /// This will count the number of lines in the file, the number of fields and setup the names of the fields (either by using the header row or setting a default value)
        /// </summary>
        private void ReadHeaderAndStats()
        {
            using (StreamReader sr = new StreamReader(this._fileName))
            {
                this.FieldNames = new Dictionary<int, string>();

                //Keep track of how many lines we have in the file
                int lineno = 0;

                //Keep track of which column we're on
                int fieldno = 0;

                string linecontents = null;

                while ((linecontents = sr.ReadLine()) != null)
                {
                    lineno++;

                    //Check if we're reading the header row
                    bool onHeaderRow;
                    if (this._headerLineNo > 0)
                    {
                        onHeaderRow = (lineno == this._headerLineNo);
                    }
                    else
                    {
                        onHeaderRow = (lineno == this._firstDataRowLineNo);
                    }

                    //If we're reading the header row, generate the names of the fields
                    if (onHeaderRow)
                    {
                        foreach (string fieldcontents in linecontents.Split(this._delim))
                        {
                            fieldno++;

                            string fieldname = this._headerLineNo > 0 ? fieldcontents : "Column " + fieldno;
                            this.FieldNames.Add(fieldno - 1, fieldname);
                        }
                    }
                }

                //We should now have a list of field names and a count of lines
                this.LineCount = lineno;
                this.FieldCount = fieldno;
            }
        }

        /// <summary>
        /// Parse the next line in the file using the configured delimiter, setting the CurrentLineContents var, CurrentFieldValues dict, and incrementing the line counts
        /// </summary>
        /// <returns>The new CurrentFieldValues or null if we have reached the end of the file</returns>
        public Dictionary<int, string> GetNextRowValues()
        {
            string linecontents;

            if (this._streamReader == null)
            {
                //Set up stream reader
                this._streamReader = new StreamReader(this._fileName);

                //Set up file
                //Read to the first line
                int lineno = 0;

                while ((linecontents = this._streamReader.ReadLine()) != null)
                {
                    lineno++;
                    if (lineno == this._firstDataRowLineNo)
                    {
                        //linecontents is now set to the first data row
                        this.CurrentLineNoAbs = lineno;
                        this.CurrentLineNoRel = 1;
                        break;
                    }
                }
            }
            else
            {
                linecontents = this._streamReader.ReadLine();

                //linecontents might be null if we're at the end of the file, but we should advance the counters anyway as we are no longer on the previous line
                this.CurrentLineNoAbs++;
                this.CurrentLineNoRel++;
            }

            Dictionary<int, string> currentFieldValues = null;
            if (linecontents != null)
            {
                int fieldno = 0;
                currentFieldValues = new Dictionary<int, string>();

                foreach (string fieldvalue in linecontents.Split(this._delim))
                {
                    fieldno++;
                    currentFieldValues.Add(fieldno - 1, fieldvalue);
                }

            }

            //Set the current line contents and field values (even if they are null as the line has changed)
            this.CurrentLineContents = linecontents;
            this.CurrentFieldValues = currentFieldValues;

            //Return the current field values. The caller is expected to check for null.
            return this.CurrentFieldValues;
        }

        /// <summary>
        /// Disposes of the StreamReader.
        /// </summary>
        public void Dispose()
        {
            if (this._streamReader != null)
            {
                this._streamReader.Dispose();
            }
        }
    }

}
